]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSMacOSX/Bonjour Safari Extension/CNServiceBrowserView.m
mDNSResponder-1096.60.2.tar.gz
[apple/mdnsresponder.git] / mDNSMacOSX / Bonjour Safari Extension / CNServiceBrowserView.m
1 /*
2 * Copyright (c) 2017-2019 Apple Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #import "CNServiceBrowserView.h"
18 #import "CNDomainBrowserPathUtils.h"
19 #include <dns_sd.h>
20
21 #import <SafariServices/SafariServices.h>
22
23 #define SHOW_SERVICETYPE_IF_SEARCH_COUNT 0
24
25 const NSString * _CNInstanceKey_fullName = @"fullName";
26 const NSString * _CNInstanceKey_name = @"name";
27 const NSString * _CNInstanceKey_serviceType = @"serviceType";
28 const NSString * _CNInstanceKey_domainPath = @"domainPath";
29 const NSString * _CNInstanceKey_resolveUrl = @"resolveUrl";
30 const NSString * _CNInstanceKey_resolveInstance = @"resolveInstance";
31
32 @interface _DNSServiceRefWrapper : NSObject
33 {
34 DNSServiceRef _ref;
35 }
36
37 - (instancetype)initWithRef:(DNSServiceRef)ref;
38 @end
39
40 @implementation _DNSServiceRefWrapper
41
42 - (instancetype)initWithRef:(DNSServiceRef)ref
43 {
44 if( self = [super init] )
45 {
46 _ref = ref;
47 }
48 return( self );
49 }
50
51 - (void)dealloc
52 {
53 if( _ref ) DNSServiceRefDeallocate( _ref );
54 }
55
56 @end
57
58 @implementation NSArray( CaseInsensitiveStringArrayCompare )
59
60 - (BOOL)caseInsensitiveStringMatch:(NSArray *)inArray
61 {
62 BOOL match = YES;
63
64 if( self.count != [inArray count] ) match = NO; // Nil zero len ok
65 else
66 {
67 NSInteger i = 0;
68 for( NSString * next in self )
69 {
70 NSString * inNext = inArray[i++];
71 if( ![inNext isKindOfClass: [NSString class]] || ![next isKindOfClass: [NSString class]] )
72 {
73 match = NO;
74 break;
75 }
76 else if( [next caseInsensitiveCompare: inNext] != NSOrderedSame )
77 {
78 match = NO;
79 break;
80 }
81 }
82 }
83
84 return( match );
85 }
86
87 @end
88
89 @protocol CNServiceTypeLocalizerDelegate <NSObject>
90 @property (strong) NSDictionary * localizedServiceTypesDictionary;
91 @end
92
93 @interface CNServiceTypeLocalizer : NSValueTransformer
94 {
95 id<CNServiceTypeLocalizerDelegate> _delegate;
96 }
97 - (instancetype)initWithDelegate:(id<CNServiceTypeLocalizerDelegate>)delegate;
98
99 @end
100
101 @implementation CNServiceTypeLocalizer
102
103 - (instancetype)initWithDelegate:(id<CNServiceTypeLocalizerDelegate>)delegate
104 {
105 if( self = [super init] )
106 {
107 _delegate = delegate;
108 }
109 return( self );
110 }
111
112 + (Class)transformedValueClass
113 {
114 return [NSString class];
115 }
116
117 + (BOOL)allowsReverseTransformation
118 {
119 return NO;
120 }
121
122 - (nullable id)transformedValue:(nullable id)value
123 {
124 id result = value;
125
126 if( value && _delegate && [_delegate respondsToSelector: @selector(localizedServiceTypesDictionary)] )
127 {
128 NSString * localizedValue = [_delegate.localizedServiceTypesDictionary objectForKey: value];
129 if( localizedValue ) result = localizedValue;
130 }
131
132 return( result );
133 }
134
135 @end
136
137 @implementation NSBrowser( PathArray )
138
139 - (NSArray *)pathArrayToColumn:(NSInteger)column includeSelectedRow:(BOOL)includeSelection
140 {
141 NSMutableArray * pathArray = [NSMutableArray array];
142 if( !includeSelection ) column--;
143 for( NSInteger c = 0 ; c <= column ; c++ )
144 {
145 NSBrowserCell *cell = [self selectedCellInColumn: c];
146 if( cell ) [pathArray addObject: [cell stringValue]];
147 }
148
149 return( pathArray );
150 }
151
152 @end
153
154 static void resolveReply( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, const char *hosttarget, uint16_t port, uint16_t txtLen, const unsigned char *txtRecord, void *context );
155 static void browseReply( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context );
156
157 @interface CNServiceBrowserView ()
158
159 @property (strong) NSTableView * instanceTable;
160 @property (strong) NSArrayController * instanceC;
161 @property (strong) NSTableColumn * instanceNameColumn;
162 @property (strong) NSTableColumn * instanceServiceTypeColumn;
163 @property (strong) NSTableColumn * instancePathPopupColumn;
164
165 @property (strong) CNServiceTypeLocalizer * serviceTypeLocalizer;
166
167 @property (strong) NSArray * currentDomainPath;
168 @property (strong) NSMutableArray * instanceRs;
169 @property (strong) NSMutableDictionary * instanceD;
170 @property (strong) NSMutableArray * instanceA;
171
172 @property (strong) dispatch_queue_t instanceBrowseQ;
173
174 @end
175
176 @implementation CNServiceBrowserView
177
178 @synthesize serviceTypes = _serviceTypes;
179
180 - (instancetype)initWithFrame:(NSRect)frameRect
181 {
182 if( self = [super initWithFrame: frameRect] )
183 {
184 [self commonInit];
185 }
186 return( self );
187 }
188
189 - (nullable instancetype)initWithCoder:(NSCoder *)coder
190 {
191 if( self = [super initWithCoder: coder] )
192 {
193 [self commonInit];
194 }
195 return( self );
196 }
197
198
199 - (void)contentViewsInit
200 {
201 NSRect frame = self.frame;
202 self.instanceC = [[NSArrayController alloc] init];
203 self.serviceTypeLocalizer = [[CNServiceTypeLocalizer alloc] initWithDelegate: (id<CNServiceTypeLocalizerDelegate>)self];
204
205 // My table view
206 NSTableView * tableView = [[NSTableView alloc] initWithFrame: frame];
207 tableView.columnAutoresizingStyle = NSTableViewFirstColumnOnlyAutoresizingStyle;
208 tableView.allowsColumnReordering = NO;
209 tableView.delegate = (id<NSTableViewDelegate>)self;
210 tableView.doubleAction = @selector( doubleAction:);
211 [tableView bind: NSContentBinding toObject: self.instanceC withKeyPath: @"arrangedObjects" options: nil];
212 self.instanceTable = tableView;
213
214 // Scroll view for table
215 NSScrollView * tableContainer = [[NSScrollView alloc] initWithFrame: frame];
216 tableContainer.autoresizingMask = (NSViewHeightSizable | NSViewWidthSizable);
217 [tableContainer setDocumentView: tableView];
218
219 // Name column
220 NSTableColumn * column = [[NSTableColumn alloc] init];
221 column.resizingMask = (NSTableColumnAutoresizingMask);
222 column.width = frame.size.width / 3;
223 column.minWidth = column.width / 2;
224 NSTextFieldCell * cell = [[NSTextFieldCell alloc] init];
225 cell.truncatesLastVisibleLine = YES;
226 column.dataCell = cell;
227 [column.headerCell setStringValue: NSLocalizedString( @"_dnsBrowser.instances.name", nil )];
228 [column bind: NSValueBinding toObject: self.instanceC withKeyPath: @"arrangedObjects.name" options: nil];
229 [tableView addTableColumn: column];
230 self.instanceNameColumn = column;
231
232 // Service type column
233 column = [[NSTableColumn alloc] init];
234 column.resizingMask = (NSTableColumnNoResizing);
235 column.width = frame.size.width / 3;
236 column.dataCell = [[NSTextFieldCell alloc] init];
237 [column.headerCell setStringValue: NSLocalizedString( @"_dnsBrowser.instances.type", nil )];
238 [column bind: NSValueBinding toObject: self.instanceC withKeyPath: @"arrangedObjects.serviceType" options: @{ NSValueTransformerBindingOption: self.serviceTypeLocalizer }];
239 [tableView addTableColumn: column];
240 self.instanceServiceTypeColumn = column;
241
242 // Path popup column
243 column = [[NSTableColumn alloc] init];
244 column.resizingMask = (NSTableColumnNoResizing);
245 column.width = frame.size.width / 3;
246 NSPopUpButtonCell * popUpCell = [[NSPopUpButtonCell alloc] init];
247 popUpCell.pullsDown = YES;
248 popUpCell.arrowPosition = NSPopUpArrowAtBottom;
249 popUpCell.autoenablesItems = YES;
250 popUpCell.preferredEdge = NSRectEdgeMaxY;
251 popUpCell.bezelStyle = NSBezelStyleTexturedSquare;
252 popUpCell.font = [NSFont systemFontOfSize: [NSFont smallSystemFontSize]];
253 column.dataCell = popUpCell;
254 [column.headerCell setStringValue: NSLocalizedString( @"_dnsBrowser.instances.domain", nil )];
255 [column bind: NSContentBinding toObject: self.instanceC withKeyPath: @"arrangedObjects.domainPath" options: nil];
256 [tableView addTableColumn: column];
257 self.instancePathPopupColumn = column;
258
259 [self addSubview: tableContainer];
260 }
261
262 - (void)commonInit
263 {
264 self.serviceTypes = @[@"_http._tcp"];
265 self.instanceRs = [NSMutableArray array];
266 self.instanceD = [NSMutableDictionary dictionary];
267 self.instanceA = [NSMutableArray array];
268
269 [self contentViewsInit];
270 }
271
272 - (void) setServiceTypes:(NSArray *)serviceTypes
273 {
274 if( ![_serviceTypes isEqualTo: serviceTypes] )
275 {
276 _serviceTypes = serviceTypes;
277 }
278 }
279
280 - (NSArray *) serviceTypes
281 {
282 return( _serviceTypes );
283 }
284
285 - (BOOL)foundInstancesWithMoreThanOneServiceType
286 {
287 BOOL result = NO;
288
289 #if SHOW_SERVICETYPE_IF_SEARCH_COUNT
290 result = (_serviceTypes.count > 1);
291 #else
292 if( _instanceD.count )
293 {
294 NSString * serviceType;
295 for( NSDictionary *next in [_instanceD allValues] )
296 {
297 if( !serviceType )
298 {
299 serviceType = next[_CNInstanceKey_serviceType];
300 continue;
301 }
302 else if( [next[_CNInstanceKey_serviceType] caseInsensitiveCompare: serviceType] != NSOrderedSame )
303 {
304 result = YES;
305 break;
306 }
307 }
308 }
309 #endif
310
311 return( result );
312 }
313
314 - (BOOL)foundInstancesInMoreThanCurrentDomainPath
315 {
316 BOOL result = NO;
317
318 if( _instanceD.count )
319 {
320 NSArray * selectedPathArray = [[_currentDomainPath reverseObjectEnumerator] allObjects];
321 if( !selectedPathArray.count ) selectedPathArray = [NSArray arrayWithObject: @"local"];
322 for( NSDictionary *next in [_instanceD allValues] )
323 {
324 if( [next[_CNInstanceKey_domainPath] caseInsensitiveStringMatch: selectedPathArray] ) continue;
325 else
326 {
327 result = YES;
328 break;
329 }
330 }
331 }
332
333 #if DEBUG_DOMAIN_POPUPS
334 return( YES );
335 #else
336 return( result );
337 #endif
338 }
339
340 #pragma mark - Notifications
341
342 - (void)tableViewSelectionDidChange:(NSNotification *)notification
343 {
344 if( _delegate && [_delegate respondsToSelector: @selector(bonjourServiceSelected:type:atDomain:)] &&
345 notification.object == self.instanceTable )
346 {
347 NSTableView * table = (NSTableView *)notification.object;
348 NSDictionary * record = nil;
349 if( table.selectedRow >= 0 && table.selectedRow < (NSInteger)[self.instanceC.content count] ) record = (NSDictionary *)self.instanceC.content[table.selectedRow];
350
351 [_delegate bonjourServiceSelected: record[_CNInstanceKey_name]
352 type: record[_CNInstanceKey_serviceType]
353 atDomain: record ? DomainPathToDNSDomain( [[record[_CNInstanceKey_domainPath] reverseObjectEnumerator] allObjects] ) : nil];
354 }
355 }
356
357
358 #pragma mark - Delegates
359
360 - (void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
361 {
362 (void)tableColumn; // Unused
363 (void)row; // Unused
364 if( tableView == self.instanceTable )
365 {
366 if( [cell isKindOfClass: [NSPopUpButtonCell class]] )
367 {
368 NSPopUpButtonCell * popCell = cell;
369 if( popCell.numberOfItems > 1 ) popCell.arrowPosition = NSPopUpArrowAtBottom;
370 else popCell.arrowPosition = NSPopUpNoArrow;
371 }
372 }
373 }
374
375 #if 0
376 - (void)tableView:(NSTableView *)tableView didClickTableColumn:(NSTableColumn *)tableColumn
377 {
378 }
379 #endif
380
381 - (void) handleBrowseResults
382 {
383 dispatch_async( dispatch_get_main_queue(), ^{
384 [self bonjourBrowserServiceBrowseUpdate: self->_instanceA];
385 });
386 }
387
388 - (void)bonjourBrowserServiceBrowseUpdate:(NSArray *)services
389 {
390 self.instanceC.content = [services sortedArrayUsingComparator: ^( id obj1, id obj2 ) {
391 return (NSComparisonResult)[ obj1[_CNInstanceKey_name] compare: obj2[_CNInstanceKey_name]];
392 }];
393 [self adjustInstancesColumnWidths];
394 }
395
396 - (void) adjustInstancesColumnWidths
397 {
398 self.instanceServiceTypeColumn.hidden = ![self foundInstancesWithMoreThanOneServiceType];
399 self.instancePathPopupColumn.hidden = ![self foundInstancesInMoreThanCurrentDomainPath];
400
401 if( !self.instanceServiceTypeColumn.hidden || !self.instancePathPopupColumn.hidden )
402 {
403 BOOL sizeChanged = NO;
404 CGFloat maxWidthType = 0;
405 CGFloat maxWidthDomain = 0;
406 BOOL needRoomForPopup = NO;
407 NSDictionary * fontAttrType = @{ NSFontAttributeName: ((NSTextFieldCell *)self.instanceServiceTypeColumn.dataCell).font };
408 NSDictionary * fontAttrDomain = @{ NSFontAttributeName: ((NSTextFieldCell *)self.instancePathPopupColumn.dataCell).font };
409
410 for( NSDictionary * next in self.instanceC.content )
411 {
412 NSString * serviceType = [self.serviceTypeLocalizer transformedValue: next[_CNInstanceKey_serviceType]];
413 NSSize nextSize = [serviceType sizeWithAttributes: fontAttrType];
414 maxWidthType = MAX( nextSize.width, maxWidthType );
415
416 NSArray * path = next[_CNInstanceKey_domainPath];
417 nextSize = [path[0] sizeWithAttributes: fontAttrDomain];
418 maxWidthDomain = MAX( nextSize.width, maxWidthDomain );
419 if( path.count > 1 ) needRoomForPopup = YES;
420 }
421
422 #define EDGE_GAP 5
423 #define POPUP_ARROW 22
424
425 if( !self.instanceServiceTypeColumn.hidden )
426 {
427 maxWidthType += (EDGE_GAP * 2);
428 if( self.instanceServiceTypeColumn.width != maxWidthType )
429 {
430 self.instanceServiceTypeColumn.width = self.instanceServiceTypeColumn.minWidth = self.instanceServiceTypeColumn.maxWidth = maxWidthType;
431 sizeChanged = YES;
432 }
433 }
434
435 if( !self.instancePathPopupColumn.hidden )
436 {
437 maxWidthDomain += (EDGE_GAP * 2) + needRoomForPopup ? POPUP_ARROW : 0;
438 if( self.instancePathPopupColumn.width != maxWidthDomain )
439 {
440 self.instancePathPopupColumn.width = self.instancePathPopupColumn.minWidth = self.instancePathPopupColumn.maxWidth = maxWidthDomain;
441 sizeChanged = YES;
442 }
443 }
444
445 if( sizeChanged )
446 {
447 [self.instancePathPopupColumn.tableView sizeToFit];
448 }
449 }
450 }
451
452 #pragma mark - Dispatch
453
454 static void finalizer( void * context )
455 {
456 CNServiceBrowserView *self = (__bridge CNServiceBrowserView *)context;
457 // NSLog( @"finalizer: %@", self );
458 (void)CFBridgingRelease( (__bridge void *)self );
459 }
460
461 #pragma mark - Commands
462
463 - (void)doubleAction:(id)sender
464 {
465 if( _delegate && [_delegate respondsToSelector: @selector(doubleAction:)] &&
466 sender == self.instanceTable )
467 {
468 NSTableView * table = (NSTableView *)sender;
469 NSDictionary * record = nil;
470 if( table.selectedRow >= 0 && table.selectedRow < (NSInteger)[self.instanceC.content count] ) record = (NSDictionary *)self.instanceC.content[table.selectedRow];
471 [_delegate doubleAction: record[_CNInstanceKey_resolveUrl]];
472 }
473 }
474
475 - (void)newServiceBrowse:(NSArray *)domainPath
476 {
477 if( _serviceTypes.count)
478 {
479 self.instanceC.content = nil;
480 [self browseForServiceTypes: _serviceTypes inDomainPath: domainPath];
481 }
482 }
483
484 - (void)browseForServiceTypes:(NSArray *)serviceTypes inDomainPath:(NSArray *)domainPath
485 {
486 if( serviceTypes.count /*&& domainPath.count*/ )
487 {
488 _serviceTypes = [serviceTypes copy];
489 _currentDomainPath = [domainPath copy];
490
491 NSString * domainStr = DomainPathToDNSDomain( _currentDomainPath );
492
493 [_instanceRs removeAllObjects];
494 if( !_instanceBrowseQ )
495 {
496 self.instanceBrowseQ = dispatch_queue_create( "DNSServiceBrowse", DISPATCH_QUEUE_PRIORITY_DEFAULT );
497 dispatch_set_context( _instanceBrowseQ, (void *)CFBridgingRetain( self ) );
498 dispatch_set_finalizer_f( _instanceBrowseQ, finalizer );
499 }
500
501 dispatch_sync( _instanceBrowseQ, ^{
502 [self->_instanceD removeAllObjects];
503 [self->_instanceA removeAllObjects];
504 });
505
506 DNSServiceErrorType error;
507 DNSServiceRef mainRef;
508 if( (error = DNSServiceCreateConnection( &mainRef )) != 0 )
509 NSLog(@"DNSServiceCreateConnection failed error: %ld", error);
510 else
511 {
512 for( NSString * nextService in _serviceTypes )
513 {
514 DNSServiceRef ref = mainRef;
515 if( (error = DNSServiceBrowse( &ref, kDNSServiceFlagsShareConnection, 0, [nextService UTF8String], [domainStr UTF8String], browseReply, (__bridge void *)self )) != 0 )
516 NSLog(@"DNSServiceBrowse failed error: %ld", error);
517 else
518 {
519 [_instanceRs addObject: [[_DNSServiceRefWrapper alloc] initWithRef: ref]];
520 }
521 }
522 [_instanceRs addObject: [[_DNSServiceRefWrapper alloc] initWithRef: mainRef]];
523 if( !error )
524 {
525 error = DNSServiceSetDispatchQueue( mainRef, _instanceBrowseQ );
526 if( error ) NSLog( @"DNSServiceSetDispatchQueue error: %d", error );
527 }
528 }
529 }
530 }
531
532 - (void)resolveServiceInstance:(NSMutableDictionary *)record
533 {
534 __weak NSDictionary * weakRecord = record;
535 DNSServiceRef ref;
536 DNSServiceErrorType error;
537 NSString * domainPath = DomainPathToDNSDomain( record[_CNInstanceKey_domainPath] );
538
539 if( (error = DNSServiceResolve( &ref, (DNSServiceFlags)0, kDNSServiceInterfaceIndexAny, [record[_CNInstanceKey_name] UTF8String], [record[_CNInstanceKey_serviceType] UTF8String], [domainPath UTF8String], resolveReply, (__bridge void *)weakRecord )) != 0 )
540 {
541 NSLog(@"DNSServiceResolve failed error: %ld", error);
542 }
543 else
544 {
545 record[_CNInstanceKey_resolveInstance] = [[_DNSServiceRefWrapper alloc] initWithRef: ref];
546 error = DNSServiceSetDispatchQueue( ref, _instanceBrowseQ );
547 if( error ) NSLog( @"resolve DNSServiceSetDispatchQueue error: %d", error );
548 }
549 }
550
551 #pragma mark - Static Callbacks
552
553 static void resolveReply( DNSServiceRef sdRef,
554 DNSServiceFlags flags,
555 uint32_t interfaceIndex,
556 DNSServiceErrorType errorCode,
557 const char *fullname,
558 const char *hosttarget,
559 uint16_t port, /* In network byte order */
560 uint16_t txtLen,
561 const unsigned char *txtRecord,
562 void *context )
563 {
564 (void)sdRef; // Unused
565 (void)flags; // Unused
566 (void)interfaceIndex; // Unused
567 (void)errorCode; // Unused
568 (void)fullname; // Unused
569 __weak NSMutableDictionary * record = (__bridge __weak NSMutableDictionary *)context;
570 if( record && hosttarget )
571 {
572 NSURLComponents * urlComponents = [[NSURLComponents alloc] init];
573 urlComponents.scheme = @"http";
574 urlComponents.host = [NSString stringWithUTF8String: hosttarget];
575 if( TXTRecordContainsKey( txtLen, txtRecord, "path" ) )
576 {
577 uint8_t valueLen;
578 const u_char * valuePtr = TXTRecordGetValuePtr( txtLen, txtRecord, "path", &valueLen );
579 urlComponents.path = (__bridge_transfer NSString *)CFStringCreateWithBytes( kCFAllocatorDefault, valuePtr, valueLen, kCFStringEncodingUTF8, false );
580 }
581 if( port ) urlComponents.port = [NSNumber numberWithUnsignedShort: ntohs( port )];
582 record[_CNInstanceKey_resolveUrl] = urlComponents.URL;
583 }
584 }
585
586 static void browseReply( DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *serviceName, const char *regtype, const char *replyDomain, void *context )
587 {
588 (void)sdRef; // Unused
589 (void)interfaceIndex; // Unused
590 (void)errorCode; // Unused
591 CNServiceBrowserView *self = (__bridge CNServiceBrowserView *)context;
592 char fullNameBuffer[kDNSServiceMaxDomainName];
593 if( DNSServiceConstructFullName( fullNameBuffer, serviceName, regtype, replyDomain ) == kDNSServiceErr_NoError )
594 {
595 NSString *fullName = @(fullNameBuffer);
596 NSString *name = [NSString stringWithUTF8String: serviceName];
597 NSArray *pathArray = DNSDomainToDomainPath( [NSString stringWithUTF8String: replyDomain] );
598
599 if( flags & kDNSServiceFlagsAdd )
600 {
601 BOOL okToAdd = YES;
602 NSString * newServiceType = [[NSString stringWithUTF8String: regtype] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString: @"."]];
603 NSString * oldServiceType = [self.instanceD objectForKey: name][_CNInstanceKey_serviceType];
604 if( oldServiceType && ![newServiceType isEqualToString: oldServiceType] )
605 {
606 NSInteger newIndex = [self.serviceTypes indexOfObject: newServiceType];
607 NSInteger oldIndex = [self.serviceTypes indexOfObject: oldServiceType];
608 if( newIndex != NSNotFound && oldIndex != NSNotFound && oldIndex < newIndex ) okToAdd = NO;
609 }
610 if( okToAdd )
611 {
612 NSMutableDictionary * record = [NSMutableDictionary dictionary];
613 record[_CNInstanceKey_fullName] = fullName;
614 record[_CNInstanceKey_name] = name;
615 record[_CNInstanceKey_serviceType] = newServiceType;
616 record[_CNInstanceKey_domainPath] = [[pathArray reverseObjectEnumerator] allObjects];
617 [self.instanceD setObject: record
618 forKey: name];
619 [self resolveServiceInstance: record];
620 }
621 }
622 else
623 {
624 NSString * newServiceType = [[NSString stringWithUTF8String: regtype] stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString: @"."]];
625 NSDictionary * oldRecord = [self.instanceD objectForKey: name];
626 if( [oldRecord[_CNInstanceKey_serviceType] isEqualToString: newServiceType] )
627 {
628 [self.instanceD removeObjectForKey: name];
629 }
630 }
631
632 if( !(flags & kDNSServiceFlagsMoreComing) )
633 {
634 dispatch_async( dispatch_get_main_queue(), ^{
635 [self.instanceA setArray: [[self.instanceD allValues] sortedArrayUsingComparator: ^( id obj1, id obj2 ) {
636 return (NSComparisonResult)[obj1[_CNInstanceKey_name] compare: obj2[_CNInstanceKey_name] options: NSCaseInsensitiveSearch];
637 }]];
638 [self handleBrowseResults];
639 });
640 }
641 }
642 }
643
644 @end